//
//  ShadowMatrix.m
//  SIO2_SDK_PVRTC_StaticLibProject
//
//  Created by Goffredo Marocchi on 8/3/10.
//  Copyright 2010 IGGS. All rights reserved.
//

#import "sio2.h"
#import "ShadowMatrix.h"
#import "LinearMath/btVector3.h"
#import "btBulletCollisionCommon.h"
#import "SIO2GL.h"

vec3 diff;
vec3 planePos;
#define SHADOW_PLANE0_SHADOW_COEFF (0.05f)
//hardcoded value... must be adapted to each plane the shadow is casted upon... if it is a large one
//the projection of the shadowCaster_to_plane distance vector over the plane's normal
//has to be greater than this value as long as the shado caster does not cross the plane.
//The engine checks the dot product of those two vectors to determine if the shadow can be casted or not
//in order to prevent backprojection (it happens when the plane is between the light and the shadow caster).

@interface ShadowMatrix ()

@property (assign) BOOL shadowMatrixIsSet;

@end

@implementation ShadowMatrix

@synthesize shadowColor, planeNormal, lightPos, lightDir, lampObj, shadowMatrixIsSet;

-(id) initWithLight:(SIO2lamp*)lamp {
	if (self = [super init]) {
		shadowMatrixIsSet = FALSE;
		lampObj = lamp;
		
		diff.x = diff.y = diff.z = 0.0f;
		planePos.x = planePos.y = planePos.z = 0.0f;
		planeNormal.x = planeNormal.y = planeNormal.z = 0.0f;
		
		NSAssert (lampObj != nil, @"Invalid light object pointer passed to the ShadowMatrix's init function");
#if DEBUG==1
		PRINT_SVEC("Light position is: ", lightPos);
		PRINT_SVEC("Normal vector is: ", planeNormal);
#endif
	}
	return self;
}

-(void)dealloc {
	
	[super dealloc];
}

-(void) setUpShadowMatrix:(const char*)objName {
	vec4 loc;
	vec3 loc3;
	
	SIO2object* plane = (SIO2object *)sio2ResourceGet(sio2->_SIO2resource, SIO2_OBJECT, (char*)objName);
	
	DPRINTF ("\n... plane's name is \"%s\"", plane->name);
	btVector3 rayTo (plane->_SIO2transform->loc->x,
					 plane->_SIO2transform->loc->y,
					 plane->_SIO2transform->loc->z);
	
	btVector3 camPos( sio2->_SIO2camera->_SIO2transform->loc->x,
					 sio2->_SIO2camera->_SIO2transform->loc->y,
					 sio2->_SIO2camera->_SIO2transform->loc->z );
	
	btCollisionWorld::AllHitsRayResultCallback collision_ray( camPos, rayTo );
	sio2->_SIO2physic->_btSoftRigidDynamicsWorld->rayTest( camPos, rayTo, collision_ray );
	
	NSAssert (collision_ray.hasHit(), @"The ray did not hit the plane"); 
	
	for (int i = 0; i < collision_ray.m_collisionObjects.size(); i++) {
		SIO2object* obj = (SIO2object*) collision_ray.m_collisionObjects.at(i)->getUserPointer();
		
		MARK;
		DPRINTF("\n(BULLET) This object's name is: %s\n\n", obj->name);
		
		if ([SIO2GL isSIO2ObjectName:[NSString stringWithUTF8String:obj->name]
					 InSIO2ObjectArray:objShadowReceiver ofSize:MAX_SHADOW_RECEIVERS]) {
			planeNormal.x = collision_ray.m_hitNormalWorld.at(i).x();
			planeNormal.y = collision_ray.m_hitNormalWorld.at(i).y();
			planeNormal.z = collision_ray.m_hitNormalWorld.at(i).z();
			
			loc.x = collision_ray.m_hitPointWorld.at(i).x();
			loc.y = collision_ray.m_hitPointWorld.at(i).y();
			loc.z = collision_ray.m_hitPointWorld.at(i).z();
			loc.w = 1.0f;
			break;
		}
	}
	
	COPYVEC4toVEC3(&loc, &loc3);
	
	COPYVEC4toVEC3(plane->_SIO2transform->loc, &planePos);
	sio2Vec3Diff(&loc3, plane->_SIO2transform->loc, &diff);
	
	loc.w = 1.0f;
}

-(void) calculateMatrix {
	vec4 nor,
	loc,
	lam = { lampObj->_SIO2transform->loc->x,
		lampObj->_SIO2transform->loc->y,
		lampObj->_SIO2transform->loc->z,
		1.0f };
	
	float dp;
	float* m = &m_shadow[0];
	
	COPYDVEC3toVEC4(&planeNormal, &nor); //COPYDVEC --> .w = 0
	COPYPVEC3toVEC4(&planePos, &loc); //COPYPVEC --> .w = 1
	
	nor.w = -( ( nor.x * loc.x ) +
			  ( nor.y * loc.y ) +
			  ( nor.z * loc.z ) );
	
	dp = ( nor.x * lam.x ) +
	( nor.y * lam.y ) +
	( nor.z * lam.z ) +
	( nor.w * lam.w );
	
	m[ 0  ] = dp - ( lam.x * nor.x );
	m[ 4  ] = -( lam.x * nor.y );
	m[ 8  ] = -( lam.x * nor.z );
	m[ 12 ] = -( lam.x * nor.w );
	
	m[ 1  ] = -( lam.y * nor.x );
	m[ 5  ] = dp - ( lam.y * nor.y );
	m[ 9  ] = -( lam.y * nor.z );
	m[ 13 ] = -( lam.y * nor.w );
	
	m[ 2  ] = -( lam.z * nor.x );
	m[ 6  ] = -( lam.z * nor.y );
	m[ 10 ] = dp - ( lam.z * nor.z );
	m[ 14 ] = -( lam.z * nor.w );   
	
	m[ 3  ] = -( lam.w * nor.x );
	m[ 7  ] = -( lam.w * nor.y );
	m[ 11 ] = -( lam.w * nor.z );
	m[ 15 ] = dp - ( lam.w * nor.w );
}

-(void) renderPlanarShadows {
	glEnableClientState(GL_VERTEX_ARRAY);
	glEnableClientState(GL_TEXTURE_COORD_ARRAY);
	
	unsigned int i = 0;
	vec3 dist;
	vec3 clipN = planeNormal;
	
	[self calculateMatrix];
	// Render the object tweaking the modelview matrix
	// to squash the object into the plane (ground)
	
	glMatrixMode(GL_MODELVIEW); //apparently redundant in this case
	glPushMatrix();
	{
		//[[EAGLView sharedGLView]enableClipPlane:0 withPlaneNormal:&clipN withOffset:(-planePos.z-0.2583f)];
		//the clip plane we just enabled, due to when and how we enabled it, is defined in word space
		
		glMultMatrixf( &m_shadow[0] );
		
		glDisable(GL_CULL_FACE);
		glDisable(GL_DEPTH_TEST);
		RGBA_WRITES_OFF();
		
		// make sure that we don't draw at any raster position more than once. Using GL_INCR
		// here is smart because the value of the area in stencil buffer wich will be drawn to
		// increases to 2, and earlier we have set the stencil buffer function to only write to
		// areas with a one. This means that a shadow will not be draw 2 times on the same area!
		// ... GL_ZERO can be used too... it is better in the case of more than 254 objects casting
		// a shadow on the same raster position...
		glStencilOp(GL_KEEP, GL_KEEP, GL_INCR);
		
		// Render our shadow objects
		while( i < sio2->_SIO2resource->n_object )
		{
			SIO2object *_SIO2object = ( SIO2object * )sio2->_SIO2resource->_SIO2object[ i ];
			
			NSAssert(_SIO2object != NULL, @"the _SIO2object is NULL");
			
			sio2Vec3Diff(_SIO2object->_SIO2transform->loc, &planePos, &dist);
			sio2Normalize(&dist, &dist);
			
			if( sio2IsStateEnabled( _SIO2object->flags, SIO2_OBJECT_SHADOW) &&
			   (sio2DotProduct(&dist, &planeNormal) > SHADOW_PLANE0_SHADOW_COEFF) ) {
				
				glTranslatef(-diff.x/4.2f, -diff.y/4.2f, -diff.z/4.2f);
				
				// Tweak the dst to force the model to render
				if( _SIO2object->dst < 0.0f)_SIO2object->dst = 1.0f;
				// Render the object without any materials
				sio2ObjectRender( _SIO2object,
								 sio2->_SIO2window,				
								 sio2->_SIO2camera,
								 0,
								 SIO2_TRANSFORM_MATRIX_BIND);
			}
			++i;
		}
		
		//[[EAGLView sharedGLView]disableClipPlane:0];
		glEnable(GL_CULL_FACE);
		glEnable(GL_DEPTH_TEST);
		glEnable(GL_LIGHTING);
		RGBA_WRITES_ON();
	}
	glPopMatrix();	
	
	MARK;
	
	glDisableClientState(GL_VERTEX_ARRAY);
	glDisableClientState(GL_TEXTURE_COORD_ARRAY);
	
	[SysTools checkGLerrors];
	SIO2_SOFT_RESET();
}

-(const float *) m_shadow {
	return [self m_shadow];
}

-(void) setObjShadowReceiver:(SIO2object **)obj {
	for (int i = 0; i < MAX_SHADOW_RECEIVERS; i++) {
		objShadowReceiver[i] = obj[i];
	}
}

@end
